/**
 * Copyright 2013 ABSir's Studio
 * 
 * All right reserved
 *
 * Create on 2013-6-13 下午4:06:49
 */
package com.absir.bean.core;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import com.absir.bean.basis.Basis;
import com.absir.bean.basis.BeanDefine;
import com.absir.bean.basis.BeanDefineMerge;
import com.absir.bean.basis.BeanScope;
import com.absir.bean.basis.Configure;
import com.absir.bean.config.IBeanDefineAware;
import com.absir.bean.config.IBeanDefineProcessor;
import com.absir.bean.config.IBeanDefineSupply;
import com.absir.bean.config.IBeanFactoryAware;
import com.absir.bean.config.IBeanFactoryStarted;
import com.absir.bean.config.IBeanObjectProcessor;
import com.absir.bean.config.IBeanSoftReferenceAware;
import com.absir.bean.config.IBeanTypeFilter;
import com.absir.core.kernel.KernelClass;
import com.absir.core.kernel.KernelLang.BreakException;
import com.absir.core.kernel.KernelLang.CallbackBreak;
import com.absir.core.kernel.KernelList;
import com.absir.core.kernel.KernelReflect;

/**
 * @author absir
 * 
 */
public class BeanFactoryProvider {

	/** beanTypeFilters */
	private List<IBeanTypeFilter> beanTypeFilters = new ArrayList<IBeanTypeFilter>();

	/** beanDefineSupplies */
	private List<IBeanDefineSupply> beanDefineSupplies = new ArrayList<IBeanDefineSupply>();

	/** beanDefineProcessors */
	private List<IBeanDefineProcessor> beanDefineProcessors = new ArrayList<IBeanDefineProcessor>();

	/** beanFactoryStarteds */
	private List<IBeanFactoryStarted> beanFactoryStarteds = new ArrayList<IBeanFactoryStarted>();

	/**
	 * @author absir
	 * 
	 */
	private static class BeanDefineOriginal extends BeanDefineWrapper {

		/** beanObject */
		Object beanObject;

		/**
		 * @param beanDefine
		 */
		private BeanDefineOriginal(BeanDefine beanDefine) {
			super(beanDefine);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see com.absir.android.bean.BeanDefineWrapper#getBeanObject()
		 */
		@Override
		public Object getBeanObject() {
			if (beanObject == null) {
				beanObject = super.getBeanObject();
			}

			return beanObject;
		}
	}

	/**
	 * @param beanTypes
	 * @param beanNameAndObjects
	 */
	public BeanFactoryProvider(Collection<Class<?>> beanTypes, Object... beanNameAndObjects) {
		Map<String, BeanDefine> beanNameDefineMap = new ConcurrentHashMap<String, BeanDefine>();
		List<IBeanDefineAware> beanDefineAwares = new ArrayList<IBeanDefineAware>();
		List<IBeanObjectProcessor> beanObjectProcessors = new ArrayList<IBeanObjectProcessor>();
		List<IBeanSoftReferenceAware> beanSoftReferenceAwares = new ArrayList<IBeanSoftReferenceAware>();
		List<IBeanFactoryAware> beanFactoryAwares = new ArrayList<IBeanFactoryAware>();
		BeanFactoryImpl beanFactory = new BeanFactoryImpl(beanNameDefineMap, beanDefineAwares, beanObjectProcessors,
				beanSoftReferenceAwares);
		List<Object> beanObjects = new ArrayList<Object>();
		int length = beanNameAndObjects.length;
		for (int i = 0; i < length; i++) {
			Object nameObject = beanNameAndObjects[i];
			if (nameObject == null || nameObject instanceof String) {
				if (i++ < length) {
					Object beanObject = beanNameAndObjects[i];
					beanFactory.registerBeanObject((String) nameObject, beanObject);
					beanObjects.add(beanObject);
					continue;
				}
			}

			if (nameObject != null) {
				beanFactory.registerBeanObject(nameObject);
				beanObjects.add(nameObject);
			}
		}

		beanFactory.registerBeanObject(beanFactory);
		registerBeanTypes(beanFactory, beanTypes, new Class<?>[] { IBeanTypeFilter.class, IBeanDefineSupply.class,
				IBeanDefineProcessor.class }, new Collection<?>[] { beanTypeFilters, beanDefineSupplies,
				beanDefineProcessors });
		KernelList.sortOrderable(beanDefineSupplies);
		KernelList.sortOrderable(beanDefineProcessors);

		Iterator<Class<?>> iterator = beanTypes.iterator();
		while (iterator.hasNext()) {
			Class<?> beanType = iterator.next();
			if (!isSupport(beanType)) {
				iterator.remove();
			}
		}

		Set<BeanDefine> beanDefineTypes = new HashSet<BeanDefine>();
		for (Class<?> beanType : beanTypes) {
			List<BeanDefine> beanDefines = null;
			for (IBeanDefineSupply beanDefineSupply : beanDefineSupplies) {
				if ((beanDefines = beanDefineSupply.getBeanDefines(beanFactory, beanType)) != null) {
					for (BeanDefine beanDefine : beanDefines) {
						if (beanDefine != null) {
							if (beanDefine instanceof BeanDefineMerge) {
								BeanDefine beanDefineType = beanFactory.getBeanDefineComponent(beanDefine
										.getBeanComponent());
								if (beanDefineType != null) {
									beanDefine = ((BeanDefineMerge) beanDefine).mergeBeanDefine(beanDefineType);
								}
							}

							for (IBeanDefineProcessor beanDefineProcessor : beanDefineProcessors) {
								if ((beanDefine = beanDefineProcessor.getBeanDefine(beanFactory, beanDefine)) == null) {
									break;
								}
							}
						}

						if (beanDefine != null) {
							beanDefineTypes.add(beanDefine);
						}
					}
				}
			}
		}

		Iterator<Entry<String, BeanDefine>> iteratorMap = beanNameDefineMap.entrySet().iterator();
		while (iteratorMap.hasNext()) {
			if (iteratorMap.next().getValue() instanceof BeanDefineOriginal) {
				iteratorMap.remove();
			}
		}

		for (BeanDefine beanDefine : beanDefineTypes) {
			beanNameDefineMap.put(beanDefine.getBeanName(), beanDefine);
		}

		for (BeanDefine beanDefine : beanDefineTypes) {
			if (IBeanDefineAware.class.isAssignableFrom(beanDefine.getBeanType())) {
				beanDefineAwares.add((IBeanDefineAware) beanDefine.getBeanObject());
			}

			if (IBeanSoftReferenceAware.class.isAssignableFrom(beanDefine.getBeanType())) {
				beanSoftReferenceAwares.add((IBeanSoftReferenceAware) beanDefine.getBeanObject());
			}

			if (IBeanObjectProcessor.class.isAssignableFrom(beanDefine.getBeanType())) {
				beanObjectProcessors.add((IBeanObjectProcessor) beanDefine.getBeanObject());
			}

			if (IBeanFactoryAware.class.isAssignableFrom(beanDefine.getBeanType())) {
				beanFactoryAwares.add((IBeanFactoryAware) beanDefine.getBeanObject());
			}

			if (IBeanFactoryStarted.class.isAssignableFrom(beanDefine.getBeanType())) {
				beanFactoryStarteds.add((IBeanFactoryStarted) beanDefine.getBeanObject());
			}
		}

		KernelList.sortOrderable(beanDefineAwares);
		KernelList.sortOrderable(beanSoftReferenceAwares);
		KernelList.sortOrderable(beanObjectProcessors);
		KernelList.sortOrderable(beanFactoryAwares);
		KernelList.sortOrderable(beanFactoryStarteds);

		for (IBeanFactoryAware beanFactoryAware : beanFactoryAwares) {
			beanFactoryAware.beforeRegister(beanFactory);
		}

		for (BeanDefine beanDefine : beanDefineTypes) {
			beanFactory.registerBeanDefine(null, beanDefine);
		}

		for (Object beanObject : beanObjects) {
			beanFactory.processBeanObject(BeanScope.SINGLETON, beanObject);
		}

		for (IBeanFactoryAware beanFactoryAware : beanFactoryAwares) {
			beanFactoryAware.afterRegister(beanFactory);
		}
	}

	/**
	 * 
	 */
	public void started() {
		BeanFactoryImpl beanFactory = BeanFactoryImpl.getInstance();
		for (IBeanFactoryStarted beanFactoryStarted : beanFactoryStarteds) {
			beanFactoryStarted.started(beanFactory);
		}

		beanFactoryStarteds.clear();
	}

	/**
	 * @param beanFactory
	 * @param beanTypes
	 * @param basisTypes
	 * @param collections
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private void registerBeanTypes(final BeanFactoryImpl beanFactory, final Collection<Class<?>> beanTypes,
			final Class<?>[] basisTypes, final Collection[] collections) {
		final List<BeanDefine> beanDefines = new ArrayList<BeanDefine>();
		for (final Class<?> beanType : beanTypes) {
			if (KernelClass.getAnnotation(beanType, Basis.class) == null) {
				continue;
			}

			BeanDefine beanDefineType = null;
			if (KernelClass.isAssignableFrom(basisTypes, beanType)) {
				String beanName = BeanDefineType.getBeanName(null, beanType);
				beanDefineType = beanFactory.getBeanDefine(beanName);
				if (beanDefineType == null || beanDefineType.getBeanType() != beanType) {
					beanDefineType = new BeanDefineOriginal(new BeanDefineType(beanName, beanType));
					beanDefines.add(beanDefineType);
					beanFactory.registerBeanDefine(beanDefineType);
				}
			}

			final BeanDefine beanDefine = beanDefineType;
			if (beanType.getAnnotation(Configure.class) != null) {
				KernelReflect.doWithDeclaredMethods(beanType, new CallbackBreak<Method>() {

					@Override
					public void doWith(Method template) throws BreakException {
						// TODO Auto-generated method stub
						if (KernelClass.isAssignableFrom(basisTypes, template.getReturnType())
								&& template.getAnnotation(Basis.class) != null) {
							if (beanDefine != null || Modifier.isStatic(template.getModifiers())) {
								String beanName = BeanDefineMethod.getBeanName(null, template);
								BeanDefine beanDefineMethod = beanFactory.getBeanDefine(beanName);
								if (beanDefineMethod == null
										|| beanDefineMethod.getBeanType() != template.getReturnType()) {
									template = KernelReflect.declaredMethod(beanType, template.getName(),
											template.getParameterTypes());
									if (template != null) {
										template.setAccessible(true);
										beanDefineMethod = new BeanDefineOriginal(new BeanDefineMethod(beanName,
												beanDefine, template));
										beanDefines.add(beanDefineMethod);
										beanFactory.registerBeanDefine(beanDefineMethod);
									}
								}
							}
						}
					}
				});
			}
		}

		int length = basisTypes.length;
		for (BeanDefine beanDefine : beanDefines) {
			Object beanObject = beanDefine.getBeanObject();
			for (int i = 0; i < length; i++) {
				if (basisTypes[i].isAssignableFrom(beanObject.getClass())) {
					(collections[i]).add(beanObject);
				}
			}
		}
	}

	/**
	 * @param beanType
	 * @return
	 */
	private boolean isSupport(Class<?> beanType) {
		for (IBeanTypeFilter beanTypeFilter : beanTypeFilters) {
			if (beanTypeFilter.match(beanType)) {
				return true;
			}
		}

		return false;
	}
}
